unit fTweeningD;

interface

uses
  Winapi.Windows,
  Winapi.Messages,
  System.SysUtils,
  System.Variants,
  System.Classes,
  System.TypInfo,
  Vcl.Graphics,
  Vcl.Forms,
  Vcl.Dialogs,
  Vcl.Controls,
  Vcl.StdCtrls,
  Vcl.ExtCtrls,
  Vcl.Samples.Spin,
  
  GLS.Scene,
  GLS.VectorTypes,
  GLS.Objects,
  GLS.Coordinates,
  GLS.SceneViewer,

  GLS.BaseClasses,
  GLS.Cadencer,
  GLS.AnimationUtils,
  GLS.VectorGeometry,
  GLS.BitmapFont,
  GLS.WindowsFont;

type

  TSinglePoint = record
    X: single;
    Y: single;
  end;

  // You can customize TAnimationState for your own purpose, you can add states or rename them, etc.
  TAnimationState = (asWaiting, asGoToStateA, asGoToStateB);

  TAnimationTime = record
    Current: Double;
    Initial: Double;
    Diff: Double;
  end;

  (*
     You can customize TAnimation for your own purpose,
     you can add InitialXValue for your custom data (Matrix or anything else).
     Note that you will maybe have to create your own Tweener function
     to use your custom Current/Target type.
  *)
  TAnimation = record
    State: TAnimationState;
    Time: TAnimationTime;
    Init: Boolean;
    Initial1iValue: Integer;
    Initial1sValue: single;
    Initial3fValue: TAffineVector;
    Initial4fValue: TGLVector;
    InitialPtValue: TSinglePoint;
  end;

  TFormTweening = class(TForm)
    GLScene1: TGLScene;
    GLSceneViewer1: TGLSceneViewer;
    GLCamera1: TGLCamera;
    GLPlane1: TGLPlane;
    Panel1: TPanel;
    Button1: TButton;
    GLCadencer1: TGLCadencer;
    Button2: TButton;
    PointA: TGLPoints;
    GLFlatText1: TGLFlatText;
    GLWindowsBitmapFont1: TGLWindowsBitmapFont;
    PointB: TGLPoints;
    GLFlatText2: TGLFlatText;
    UseCurrentPosition: TCheckBox;
    EaseTypeA: TComboBox;
    EaseTypeB: TComboBox;
    TimeA: TSpinEdit;
    TimeB: TSpinEdit;
    Label2: TLabel;
    Label1: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    procedure GLPlane1Progress(Sender: TObject; const deltaTime, newTime: Double);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure GLSceneViewer1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure FormCreate(Sender: TObject);
  private
    FGLPlane1AnimationData: TAnimation;
  public
  end;

var
  FormTweening: TFormTweening;

implementation

{$R *.dfm}

procedure TFormTweening.Button1Click(Sender: TObject);
begin
  FGLPlane1AnimationData.State := asGoToStateA;
  FGLPlane1AnimationData.Init := True;
end;

procedure TFormTweening.Button2Click(Sender: TObject);
begin
  FGLPlane1AnimationData.State := asGoToStateB;
  FGLPlane1AnimationData.Init := True;
end;

procedure TFormTweening.FormCreate(Sender: TObject);
var
  EaseName: string;
  i: integer;
begin
  // Fill combobox with human readable data from TEaseType
  for i := Ord(etLinear) to Ord(etBounceOutIn) do
  begin
    EaseName := GetEnumName(TypeInfo(TEaseType), Ord(TEaseType(i)));
    EaseTypeA.Items.Add(EaseName);
    EaseTypeB.Items.Add(EaseName);
  end;

  EaseTypeA.ItemIndex := Ord(etLinear);
  EaseTypeB.ItemIndex := Ord(etLinear);
end;

procedure TFormTweening.GLPlane1Progress(Sender: TObject; const deltaTime, newTime: Double);
begin

  // Execute following code only if we are in state asGoToStateA
  if FGLPlane1AnimationData.State = asGoToStateA then
  begin
    // First we initialize value if requested
    if FGLPlane1AnimationData.Init = True then
      with FGLPlane1AnimationData do
      begin
        Init := False;
        Time.Initial := newTime;

        if UseCurrentPosition.Checked then
          Initial3fValue := GLPlane1.Position.AsAffineVector
        else
          Initial3fValue := PointB.Position.AsAffineVector;
      end;

    // Then we use the Tweener function to compute interpolation based on Initial values (time and position) and current ones
    with FGLPlane1AnimationData do
    begin
      Time.Diff := newTime - Time.Initial;
      GLPlane1.Position.AsAffineVector := Tweener(Initial3fValue, PointA.Position.AsAffineVector, Time.Diff, TimeA.Value/1000, TEaseType(EaseTypeA.ItemIndex));

      if Time.Diff >= TimeA.Value/1000 then
        State := asWaiting;
    end;
  end;

  // Execute following code only if we are in state asGoToStateB
  if FGLPlane1AnimationData.State = asGoToStateB then
  begin
    // First we initialize value if requested
    if FGLPlane1AnimationData.Init = True then
      with FGLPlane1AnimationData do
      begin
        Init := False;
        Time.Initial := newTime;

        if UseCurrentPosition.Checked then
          Initial3fValue := GLPlane1.Position.AsAffineVector
        else
          Initial3fValue := PointA.Position.AsAffineVector;
      end;

    // Then we use the Tweener function to compute interpolation based on Initial values (time and position) and current ones
    with FGLPlane1AnimationData do
    begin
      Time.Diff := newTime - Time.Initial;
      GLPlane1.Position.AsAffineVector := Tweener(Initial3fValue, PointB.Position.AsAffineVector, Time.Diff, TimeB.Value/1000, TEaseType(EaseTypeB.ItemIndex));

      if Time.Diff >= TimeB.Value/1000 then
        State := asWaiting;
    end;
  end;

end;

procedure TFormTweening.GLSceneViewer1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if ssCtrl in Shift then
  begin
    if (Button = TMouseButton.mbLeft) then
    begin
      PointA.Position.X := X;
      PointA.Position.Y := GLSceneViewer1.Height-Y;
    end
    else if (Button = TMouseButton.mbRight) then
    begin
      PointB.Position.X := X;
      PointB.Position.Y := GLSceneViewer1.Height-Y;
    end;
  end;
end;

end.
